import es

from path import path

__all__ = ["Event", "EVENT_NONETYPE", "EVENT_STRING", "EVENT_INTEGER",
           "EVENT_BOOL", "EVENT_FLOAT", "EventAttribute", "loadEventsFromFile"]

EVENT_NONETYPE = 0
EVENT_STRING   = 1 << 0
EVENT_INTEGER  = 1 << 1
EVENT_BOOL     = 1 << 2
EVENT_FLOAT    = 1 << 3

class Event(dict):
    """
    This object being a custom dictionary attribute makes it easy for use to
    create custom events. This object should be used for managing the events,
    and allow dictionary style attribute assignment for assigning event
    attributes.
    """
    def __init__(self, eventName, *args, **kw):
        """
        Constructor, ensure that we initialise the event and execute the
        dictionary constructor as well.
        
        @param str eventName The name of the event
        """
        dict.__init__(self, *args, **kw)
        self.name = eventName
        self.fired = False
        es.event("initialize", self.name)
    
    def __del__(self):
        """
        Default destructor, if the event hasn't fired yet, ensure that we cancel
        it so that the event is left unfired and thus has the potential to crash
        / lag servers.
        """
        if self.fired is False:
            es.event("cancel", self.name)
    
    def __setitem__(self, key, value):
        """
        Override the virtual __setitem__ function from the dict object. This
        will hook dictionary attribute assignment and allow us to ensure that
        any object being assigned will be an EventAttribute object. This means
        that as such self[item] = value, the value will be transferred into
        and EventAttribute object.
        
        @param mixed key The name of the event attribute
        @param mixed value The value of the event attribute
        """
        if isinstance(value, EventAttribute):
            dict.__setitem__(self, key, value)
        else:
            eventAttribute = EventAttribute(key, value)
            dict.__setitem__(self, key, eventAttribute)
            
    def __getitem__(self, key):
        """
        Override the virtual __getitem__ function from the dict object. This
        will hook dictionary attribute retrieval and allow us to ensure that
        the value returned is the value of the EventAttribute rather than that
        object itself.
        
        @param mixed key The name of the event attribute
        @return mixed The value of the event attribute
        """
        return dict.__getitem__(self, key).value
    
    def addEventVar(self, name, value, valueType=EVENT_NONETYPE):
        """
        Similarly to the __getitem__ virtual override, this funtion ensures
        that we assign an EventAttribute to the dictionary object by the given
        attribute name. This function extends the capability by allowing us to
        specify the value type rather than attempting to retrieve it
        automatically.
        
        @param str name The name of the event attribute
        @param mixed value The value of the event attribute
        @param int valueType The type of the event attribute
        """
        self.__setitem__(name, EventAttribute(name, value, valueType))
    
    def fire(self):
        """
        Fires the event if it hasn't already been fired. This will set all the
        event attributes and then fire the event which will globalise it to all
        other scripts.
        
        @return bool Whether or not the event was successfully fired
        """
        if self.fired is False:
            for eventType in self.itervalues():
                es.event(eventType.getEventTypeAsString(), self.name, 
                         eventType.name, eventType.value)
            es.event("fire", self.name)
            self.fired = True
            return True
        return False
            
class EventAttribute(object):
    """
    This object has a single purpose which allows us to give the attribute a
    specific value, and then let this object work out which type we should
    assign to the event. This means that the user need not know the type of
    the object assigning to the event attribute, as this attempts to
    automatically set it.
    """
    def __init__(self, name, value, valueType=EVENT_NONETYPE):
        """
        Constructor, initialise all the default variables and attempt to work
        out the value type if not explicitly passed.
        
        @param str name The name of the event attribute
        @param mixed value The value of the event attribute
        @param int valueType An explicit override of the value type.
        """
        if valueType == EVENT_NONETYPE:
            if isinstance(value, (int, long)):
                valueType = EVENT_INTEGER
            elif isinstance(value, float):
                valueType = EVENT_FLOAT
            elif isinstance(value, bool):
                valueType = EVENT_BOOL
                if isinstnace(value, bool):
                    value = int(value)
                elif not isinstance(value, int):
                    raise ValueError("Type bool must be of type integer or" +
                       " bool, recevied type '%s'" % type(value).__name__)
            elif isinstance(value, basestring):
                valueType = EVENT_STRING
        if valueType == EVENT_STRING:
            if not value:
                value = "0"
        self.name = name
        self.value = value
        self.valueType = valueType
   
    def __str__(self):
        """
        Executed when the option is cast to a string. Ensure that we return a 
        string representation of this object; i.e the event type as a string
        (which should be in a format valid to es.event)
        
        @return str The object type as a string
        """
        return self.getEventTypeAsString()
    
    def getEventTypeAsString(self):
        """
        Returns the object type. The return value should be able to be injected
        into es.event to ensure that the object type is set to the value. Valid
        return values are 'setstring', 'setint' and 'setfloat'. Ensure that
        the return values represent this accurately.
        
        @return str The object type as a string
        """
        if self.valueType == EVENT_NONETYPE:
            raise AttributeError("No type string for type 'EVENT_NONETYPE'")
        elif self.valueType == EVENT_INTEGER:
            return "setint"
        elif self.valueType == EVENT_FLOAT:
            return "setfloat"
        elif self.valueType == EVENT_STRING:
            return "setstring"
        elif self.valueType == EVENT_BOOL:
            return "setint"
        
def loadEventsFromFile(filePath):
    """
    Loads events from a file. This declares any custom events. Any event
    decleration only lasts for a map. This should be executed each map change
    for each event resource file.
    
    @param str|path filePath A path to the location of the resource file
    """
    if not isinstance(filePath, path):
        filePath = path(filePath)
    if not filePath.exists():
        raise ValueError("Event resourced file path %s does not exist" % 
                         filePath)
    es.loadevents("declare", str(filePath))